import type { Method, RequestElements } from 'alova'

const isString = (data: unknown): boolean => typeof data === 'string'

const globalToString = (data: unknown): string => {
  return Object.prototype.toString.call(data)
}

const instanceOf = (data: unknown, type: unknown): boolean => {
  return Object.prototype.toString.call(data) === `[object ${type}]`
}

const isSpecialRequestBody = (data: unknown): boolean => {
  const dataTypeString = globalToString(data)
  return /^\[object (Blob|FormData|ReadableStream|URLSearchParams)\]$/i.test(dataTypeString) || instanceOf(data, ArrayBuffer)
}

const isBodyData = (data: unknown): boolean => isString(data) || isSpecialRequestBody(data)
const customAdapterFetch = () => {
  return (elements: RequestElements, method: Method) => {
    const adapterConfig = method.config
    const timeout = adapterConfig.timeout || 0
    const ctrl = new AbortController()
    const { data, headers: _ } = elements
    // const isContentTypeSet = /content-type/i.test(Object.keys(headers).join())
    // const isDataFormData = data && data.toString() === '[object FormData]'
    // if (!isContentTypeSet && !isDataFormData) headers['Content-Type'] = 'application/json;charset=UTF-8'
    const fetchPromise = fetch(elements.url, {
      ...adapterConfig,
      method: elements.type,
      signal: ctrl.signal,
      body: isBodyData(data) ? data : JSON.stringify(data)
    })
    let abortTimer = null
    let isTimeout = false
    if (timeout > 0) abortTimer = setTimeout(() => {
      isTimeout = true
      ctrl.abort()
    }, timeout)
    return {
      response: () => fetchPromise.then((response) => {
        if (abortTimer) clearTimeout(abortTimer)
        return response.clone()
      }, (err) => Promise.reject(isTimeout ? new Error('fetchError: network timeout') : err)),
      headers: () => fetchPromise.then(({ headers: responseHeaders }) => responseHeaders, () => ({})),
      onDownload: async (cb: (loaded: number, total: number) => void) => {
        let isAborted = false
        const response = await fetchPromise.catch(() => {
          isAborted = true
        })
        if (!response) return
        const { headers: responseHeaders, body } = response.clone()
        const reader = body ? body.getReader() : undefined
        const total = Number(responseHeaders.get('Content-Length') || responseHeaders.get('content-length') || 0)
        if (total <= 0) return
        let loaded = 0
        if (reader) {
          const pump = async (): Promise<void | undefined> => {
            return reader.read().then(({ done, value = new Uint8Array() }): Promise<void> | undefined => {
              if (done || isAborted) {
                if (!isAborted) cb(loaded, total)
              } else {
                loaded += value.byteLength
                cb(loaded, total)
                return pump()
              }
            })
          }
          pump()
        }
      },
      onUpload() {
        console.error("fetch API does'nt support uploading progress. please consider to change `@alova/adapter-xhr` or `@alova/adapter-axios`")
      },
      abort: () => {
        ctrl.abort()
        if (abortTimer) clearTimeout(abortTimer)
      }
    }
  }
}

export default customAdapterFetch